package be.nepherte.movmanager.client.ui.components;

import be.nepherte.movmanager.client.exceptions.NoSuchGenreException;
import be.nepherte.movmanager.domain.Genre;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.Element;

/**
 * This class represents a genre picker.
 * 
 * <p>
 * This file is part of MovManager.
 * </p>
 * 
 * <p>
 * MovManager is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * 
 * <p>
 * MovManager is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManager. If not, see <a
 * href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses</a>.
 * </p>
 * 
 * @author	Bart Verhoeven
 * @version	0.1
 */
public class GenrePicker extends Composite implements HasValue<Genre> {
	/**Panel where everything is placed upon.*/
	private final VerticalPanel pnlGenrePicker;
	/**Title acting as a header for this genre picker.*/
	private final Label title;
	/**Grid with the actual genres in place.*/
	private final GenreGrid grid;
	/**Last clicked genre.*/
	private Genre clickedGenre;
	
	/**
	 * Grid with a highlight effect filled with all current genres.
	 * 
	 * @author	Bart Verhoeven
	 * @version	0.1
	 */
	private class GenreGrid extends Grid {
		/**List with all current genres.*/
		private final Genre[] genres = Genre.values();
		/**Position of the last generated event. First index holds row, second index holds column.*/
		private int[] oldEventPosition;

		/**
		 * Creates a new genre grid.
		 */
		public GenreGrid(int columns) {
			super(Genre.values().length/3,columns);
			initGrid();
		}

		/**
		 * Initializes this grid.
		 */
		private void initGrid() {
			formatGrid();
			fillGrid();
			new MouseGridHandler(this);
		}

		/**
		 * Formats this grid.
		 */
		private void formatGrid() {
			setBorderWidth(0);
			setCellPadding(3);
			setCellSpacing(0); // avoid white gaps around cells

			RowFormatter rf = getRowFormatter();
			// alternating row colors
			for (int i = 0; i < getRowCount(); i++) {
				if (i % 2 == 0)
					rf.addStyleName(i, "genrePickerGenreWhite");
				else
					rf.addStyleName(i, "genrePickerGenreGray");
			}
		}

		/**
		 * Fills the grid with genres. Genres are placed in the grid
		 * alphabetically from left to right.
		 */
		private void fillGrid() {
			int columns = getCellCount(0);
			for (int i = 0; i < genres.length; i++) {
				setText(i/columns, i%columns, genres[i].toString());
			}
		}

		/**
		 * Gets the position of the cell that caused the event. First index
		 * holds row, second index holds column.
		 * 
		 * @param	event
		 * 			the mouse event of which you want the cause
		 * @return	position of the cell that caused the event
		 */
		public int[] getCellForEvent(MouseEvent<?> event) {
			final Element td = getEventTargetCell(Event.as(event.getNativeEvent()));
			
			if (td == null)
				return null;

			final Element tr = DOM.getParent(td);
			final Element table = DOM.getParent(tr);
			
			int returnEventPosition[] = new int[2];
			returnEventPosition[0] = DOM.getChildIndex(table, tr);
			returnEventPosition[1] = DOM.getChildIndex(tr, td);
			return returnEventPosition;

		}
		
		/**
		 * MouseGridHandler to simulate highlight mouse over effect for GenreGrid.
		 * 
		 * @author	Bart Verhoeven
		 * @version	0.1
		 */
		private class MouseGridHandler implements MouseMoveHandler, MouseOutHandler {
			private final GenreGrid grid;
			private final CellFormatter cf;
			
			/**
			 * Creates a new mouse grid handler for a GenreGrid.
			 * 
			 * @param	grid
			 * 			the grid to which this MouseGridHandler is attached
			 */
			public MouseGridHandler(GenreGrid grid) {
				this.grid = grid;
				this.cf = grid.getCellFormatter();
				setMouseGridHandlers();
			}
			
			/**
			 * Adds this mouse grid handler to grid for MouseOutEvent and
			 * MouseMoveEvent.
			 */
			private void setMouseGridHandlers() {
				grid.addDomHandler(this, MouseMoveEvent.getType());
				grid.addDomHandler(this, MouseOutEvent.getType());
			}

			/**
			 * Adds highlight to the genre underneath the mouse cursor. Only works when
			 * a MouseOverEvent highlighted the initial genre.
			 */
			@Override
			public void onMouseMove(MouseMoveEvent event) {
				int[] eventPosition = grid.getCellForEvent(event);
				
				// no old position: the mouse just hovered over the genre so highlight
				if (oldEventPosition == null) {
					eventPosition = grid.getCellForEvent(event);
					oldEventPosition = eventPosition;
					cf.addStyleName(eventPosition[0], eventPosition[1], "highlightGenreCell");
					return;
					
				}
				
				// same position: do nothing
				if (oldEventPosition[0] == eventPosition[0] && oldEventPosition[1] == eventPosition[1])
					return;
				
				// new mouse position: remove highlight of old genre and highlight new one
				if (oldEventPosition[0] != eventPosition[0] || oldEventPosition[1] != eventPosition[1]) {
					cf.removeStyleName(oldEventPosition[0], oldEventPosition[1], "highlightGenreCell");
					oldEventPosition = eventPosition;
					cf.addStyleName(eventPosition[0], eventPosition[1], "highlightGenreCell");
					return;
				}
			}

			/**
			 * Removes highlight when mouse hovers out of the genre picker.
			 */
			@Override
			public void onMouseOut(MouseOutEvent event) {
				int[] eventPosition = grid.getCellForEvent(event);
				oldEventPosition = null;
				cf.removeStyleName(eventPosition[0], eventPosition[1], "highlightGenreCell");				
			}
		}
	}
	
	/**
	 * Creates a new genre picker.
	 */
	public GenrePicker() {
		this.pnlGenrePicker = new VerticalPanel();
		this.title = new Label("Genres");
		this.grid = new GenreGrid(3);
		initGenrePicker();
	}
	
	/**
	 * Initializes this genre picker;
	 */
	private void initGenrePicker()  {
		initWidget(pnlGenrePicker);
		formatGenrePicker();
		grid.addClickHandler(new GenrePickerHandler());
		pnlGenrePicker.add(title);
		pnlGenrePicker.add(grid);
	}
	
	/**
	 * Formats this genre picker.
	 */
	private void formatGenrePicker() {
		this.setStylePrimaryName("genrePicker");
		title.addStyleName("genrePickerTitle");
	}
	
	@Override
	public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Genre> handler) {
		return addHandler(handler, ValueChangeEvent.getType());
	}

	@Override
	public Genre getValue() {
		return clickedGenre;
	}

	@Override
	public void setValue(Genre value) {
		setValue(value,false);
	}

	@Override
	public void setValue(Genre value, boolean fireEvents) {
		Genre oldValue = clickedGenre;
		clickedGenre = value;
		if (fireEvents)
			ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
	}
	
	/**
	 * Handler for genre picker.
	 * 
	 * @author	Bart Verhoeven
	 * @version	0.1
	 */
	private class GenrePickerHandler implements ClickHandler {
		@Override
		public void onClick(ClickEvent event) {
			Cell c = grid.getCellForEvent(event);
			String genre = grid.getText(c.getRowIndex(), c.getCellIndex());
			try {
				setValue(Genre.getGenre(genre),true);
			} catch (NoSuchGenreException e) {
				// should not happen: lstGenres is filled with with Genre.values() so only known genres are used
			}
		}
	}
}